/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License");  you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * http//www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
 * the specific language governing rights and limitations under the License.
 *
 * The Original Code is ART OWL API.
 *
 * The Initial Developer of the Original Code is University of Roma Tor Vergata.
 * Portions created by University of Roma Tor Vergata are Copyright (C) 2009.
 * All Rights Reserved.
 *
 * The ART OWL API were developed by the Artificial Intelligence Research Group
 * (art.uniroma2.it) at the University of Roma Tor Vergata
 * Current information about the ART OWL API can be obtained at 
 * http://art.uniroma2.it/owlart
 *
 */

package it.uniroma2.art.owlart.utilities;

import it.uniroma2.art.owlart.exceptions.ModelAccessException;
import it.uniroma2.art.owlart.model.ARTBNode;
import it.uniroma2.art.owlart.model.ARTLiteral;
import it.uniroma2.art.owlart.model.ARTNode;
import it.uniroma2.art.owlart.model.ARTResource;
import it.uniroma2.art.owlart.model.ARTStatement;
import it.uniroma2.art.owlart.model.ARTURIResource;
import it.uniroma2.art.owlart.models.OWLModel;
import it.uniroma2.art.owlart.models.impl.LiteralIteratorFilteringNodeIterator;
import it.uniroma2.art.owlart.models.impl.LiteralIteratorWrappingNodeIterator;
import it.uniroma2.art.owlart.models.impl.OWLModelImpl;
import it.uniroma2.art.owlart.models.impl.ObjectsOfStatementsIterator;
import it.uniroma2.art.owlart.models.impl.PredicatesOfStatementsIterator;
import it.uniroma2.art.owlart.models.impl.ResourceIteratorFilteringNodeIterator;
import it.uniroma2.art.owlart.models.impl.ResourceIteratorWrappingNodeIterator;
import it.uniroma2.art.owlart.models.impl.SubjectsOfStatementsIterator;
import it.uniroma2.art.owlart.models.impl.URIResourceIteratorFilteringNodeIterator;
import it.uniroma2.art.owlart.models.impl.URIResourceIteratorFilteringResourceIterator;
import it.uniroma2.art.owlart.models.impl.URIResourceIteratorWrappingNodeIterator;
import it.uniroma2.art.owlart.models.impl.URIResourceIteratorWrappingResourceIterator;
import it.uniroma2.art.owlart.navigation.ARTBNodeIterator;
import it.uniroma2.art.owlart.navigation.ARTLiteralIterator;
import it.uniroma2.art.owlart.navigation.ARTNodeIterator;
import it.uniroma2.art.owlart.navigation.ARTResourceIterator;
import it.uniroma2.art.owlart.navigation.ARTStatementIterator;
import it.uniroma2.art.owlart.navigation.ARTURIResourceIterator;
import it.uniroma2.art.owlart.navigation.RDFIterator;
import it.uniroma2.art.owlart.navigation.RDFIteratorImpl;
import it.uniroma2.art.owlart.vocabulary.RDF;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;

/**
 * Utility methods for managing {@link RDFIterator}s
 * 
 * @author Armando Stellato
 * 
 */
public class RDFIterators {

	/**
	 * this method obtains a collection of objects extracted from an {@link RDFIterator}. Note that the
	 * RDFIterator is automatically closed at the end of execution of this method
	 * 
	 * @param <T>
	 * @param rdfIt
	 * @return
	 * @throws ModelAccessException
	 */
	public static <T> void addIteratorToCollection(RDFIterator<T> rdfIt, Collection<T> collection)
			throws ModelAccessException {
		while (rdfIt.streamOpen()) {
			collection.add(rdfIt.getNext());
		}
		rdfIt.close();
	}
	
	
	public static <T> T getFirst(RDFIterator<T> rdfIt) throws ModelAccessException {
		T result = null;
		if (rdfIt.streamOpen())
			result = rdfIt.getNext();
		rdfIt.close();
		return result; 
	}
	
	
	/**
	 * this method obtains a collection of objects extracted from an {@link RDFIterator}. Note that the
	 * RDFIterator is automatically closed at the end of execution of this method
	 * 
	 * @param <T>
	 * @param rdfIt
	 * @return
	 * @throws ModelAccessException
	 */
	public static <T> Collection<T> getCollectionFromIterator(RDFIterator<T> rdfIt)
			throws ModelAccessException {
		ArrayList<T> collection = new ArrayList<T>();
		while (rdfIt.streamOpen()) {
			collection.add(rdfIt.getNext());
		}
		rdfIt.close();
		return collection;
	}

	/**
	 * this method obtains a set of objects (i.e. removes duplicates) extracted from an {@link RDFIterator}.
	 * Note that the RDFIterator is automatically closed at the end of execution of this method
	 * 
	 * @param <T>
	 * @param rdfIt
	 * @return
	 * @throws ModelAccessException
	 */
	public static <T> Set<T> getSetFromIterator(RDFIterator<T> rdfIt) throws ModelAccessException {
		HashSet<T> set = new HashSet<T>();
		while (rdfIt.streamOpen()) {
			set.add(rdfIt.getNext());
		}
		rdfIt.close();
		return set;
	}

	/**
	 * creates an ARTNodeIterator iterating over all the elements pointed by <code>list</code> saving all the
	 * code needed to parse the list RDF structure
	 * 
	 * @param model
	 * @param list
	 * @param inference
	 * @param graphs
	 * @return
	 * @throws ModelAccessException
	 */
	public static ARTNodeIterator createRDFListIterator(OWLModel model, ARTResource list, boolean inference,
			ARTResource... graphs) throws ModelAccessException {
		return new RDFListIterator(model, list, inference, graphs);
	}

	/**
	 * This is to be used if the developer knows in advance that wrapped iterator contains only Literals. It
	 * is more performant than the {@link #filterLiterals(ARTNodeIterator)} call because it omits checks on
	 * the nature of the wrapped nodes, expecting them to be Literals
	 * 
	 * @param it
	 * @return
	 * @throws ModelAccessException
	 */
	public static ARTLiteralIterator toLiteralIterator(ARTNodeIterator it) throws ModelAccessException {
		return new LiteralIteratorWrappingNodeIterator(it);
	}

	/**
	 * This is to be used if the developer knows in advance that wrapped iterator contains only Resources. It
	 * is more performant than the {@link #filterResources(ARTNodeIterator)} call because it omits checks on
	 * the nature of the wrapped nodes, expecting them to be Resources
	 * 
	 * @param it
	 * @return
	 * @throws ModelAccessException
	 */
	public static ARTResourceIterator toResourceIterator(ARTNodeIterator it) throws ModelAccessException {
		return new ResourceIteratorWrappingNodeIterator(it);
	}

	/**
	 * This is to be used if the developer knows in advance that wrapped iterator contains only URIs. It is
	 * more performant than the {@link #filterURIs(ARTNodeIterator)} call because it omits checks on the
	 * nature of the wrapped nodes, expecting them to be URIs
	 * 
	 * @param it
	 * @return
	 * @throws ModelAccessException
	 */
	public static ARTURIResourceIterator toURIResourceIterator(ARTNodeIterator it)
			throws ModelAccessException {
		return new URIResourceIteratorWrappingNodeIterator(it);
	}

	/**
	 * This is to be used if the developer knows in advance that wrapped iterator contains only URIs. It is
	 * more performant than the {@link #filterURIs(ARTResourceIterator)} call because it omits checks on the
	 * nature of the wrapped nodes, expecting them to be URIs
	 * 
	 * @param it
	 * @return
	 * @throws ModelAccessException
	 */
	public static ARTURIResourceIterator toURIResourceIterator(ARTResourceIterator it)
			throws ModelAccessException {
		return new URIResourceIteratorWrappingResourceIterator(it);
	}

	/**
	 * this filtered iterator passes only {@link ARTLiteral}s out of a generic {@link ARTNodeIterator}
	 * 
	 * @param it
	 * @return
	 * @throws ModelAccessException
	 */
	public static ARTLiteralIterator filterLiterals(ARTNodeIterator it) throws ModelAccessException {
		return new LiteralIteratorFilteringNodeIterator(it);
	}

	/**
	 * this filtered iterator passes only {@link ARTResource}s out of a generic {@link ARTNodeIterator}
	 * 
	 * @param it
	 * @return
	 * @throws ModelAccessException
	 */
	public static ARTResourceIterator filterResources(ARTNodeIterator it) throws ModelAccessException {
		return new ResourceIteratorFilteringNodeIterator(it);
	}

	/**
	 * this filtered iterator passes only {@link ARTURIResource}s out of a generic {@link ARTNodeIterator}
	 * 
	 * @param it
	 * @return
	 * @throws ModelAccessException
	 */
	public static ARTURIResourceIterator filterURIs(ARTNodeIterator it) throws ModelAccessException {
		return new URIResourceIteratorFilteringNodeIterator(it);
	}

	/**
	 * this filtered iterator passes only {@link ARTURIResource}s out of a generic {@link ARTResourceIterator}
	 * 
	 * @param it
	 * @return
	 * @throws ModelAccessException
	 */
	public static ARTURIResourceIterator filterURIs(ARTResourceIterator it) throws ModelAccessException {
		return new URIResourceIteratorFilteringResourceIterator(it);
	}

	public static ARTResourceIterator listSubjects(ARTStatementIterator statIt) {
		return new SubjectsOfStatementsIterator(statIt);
	}

	public static ARTURIResourceIterator listPredicates(ARTStatementIterator statIt) {
		return new PredicatesOfStatementsIterator(statIt);
	}

	public static ARTNodeIterator listObjects(ARTStatementIterator statIt) {
		return new ObjectsOfStatementsIterator(statIt);
	}

	public static ARTNodeIterator createARTNodeIterator(RDFIterator<ARTNode> it) {
		return new ARTNodeIteratorImpl(it);
	}

	public static ARTBNodeIterator createARTBNodeIterator(RDFIterator<ARTBNode> it) {
		return new ARTBNodeIteratorImpl(it);
	}

	public static ARTResourceIterator createARTResourceIterator(RDFIterator<ARTResource> it) {
		return new ARTResourceIteratorImpl(it);
	}

	public static ARTURIResourceIterator createARTURIResourceIterator(RDFIterator<ARTURIResource> it) {
		return new ARTURIResourceIteratorImpl(it);
	}

	public static ARTURIResourceIterator createARTURIResourceIterator(Iterator<ARTURIResource> it) {
		return new ARTURIResourceIteratorWrappingStdIteratorImpl(it);
	}

	public static ARTLiteralIterator createARTLiteralIterator(RDFIterator<ARTLiteral> it) {
		return new ARTLiteralIteratorImpl(it);
	}

	public static ARTStatementIterator createARTStatementIterator(RDFIterator<ARTStatement> it) {
		return new ARTStatementIteratorImpl(it);
	}

	public static ARTStatementIterator createARTStatementIterator(Iterator<ARTStatement> it) {
		return new ARTStatementIteratorNormalIteratorImpl(it);
	}

	/**
	 * @param <T>
	 * @param it1
	 * @param it2
	 * @return an iterator iterating over the concatenation of results from <code>it1</code> and
	 *         <code>it2</code>.
	 */
	public static <T> RDFIterator<T> concat(RDFIterator<T> it1, RDFIterator<T> it2) {
		ConcatenatedRDFIterator<T> it = new ConcatenatedRDFIterator<T>(it1, it2);
		return it;
	}

	/**
	 * @param it
	 * @param singleton
	 * @return an iterator iterating over elements in iterator <code>it</code> plus the single element
	 *         <code>singleton</code> (tailed to them)
	 */
	public static <T> RDFIterator<T> concat(RDFIterator<T> it, T singleton) {
		ConcatenatedRDFIterator<T> concIt = new ConcatenatedRDFIterator<T>(it, new SingletonRDFIterator<T>(
				singleton));
		return concIt;
	}

	/**
	 * @param singleton
	 * @param it
	 * @return an iterator iterating over elements in iterator <code>it</code> plus the single element
	 *         <code>singleton</code> (put in the head)
	 */
	public static <T> RDFIterator<T> concat(T singleton, RDFIterator<T> it) {
		ConcatenatedRDFIterator<T> concIt = new ConcatenatedRDFIterator<T>(new SingletonRDFIterator<T>(
				singleton), it);
		return concIt;
	}

	private static class SingletonRDFIterator<T> extends RDFIteratorImpl<T> {

		boolean returned;
		T singleton;

		SingletonRDFIterator(T singleton) {
			this.singleton = singleton;
		}

		public boolean streamOpen() throws ModelAccessException {
			return !returned;
		}

		public T getNext() throws ModelAccessException {
			returned = true;
			return singleton;
		}

		public void close() throws ModelAccessException {
		}

	}

	private static class ConcatenatedRDFIterator<T> extends RDFIteratorImpl<T> {

		RDFIterator<T> it1;
		RDFIterator<T> it2;

		RDFIterator<T> currentIterator;

		ConcatenatedRDFIterator(RDFIterator<T> it1, RDFIterator<T> it2) {
			this.it1 = it1;
			this.it2 = it2;
			currentIterator = it1;
		}

		public boolean streamOpen() throws ModelAccessException {
			if (currentIterator.streamOpen())
				return true;
			if (currentIterator == it1) {
				it1.close();
				currentIterator = it2;
				return it2.streamOpen();
			}
			return false;
		}

		public T getNext() throws ModelAccessException {
			try {
				return currentIterator.getNext();
			} catch (NoSuchElementException e) {
				if (currentIterator == it1) {
					// there's no guarantee that a call to getNext has been anticipated by a check with
					// streamOpen(), so it is mandatory to redo the check, just in case a
					// NoSuchElementException is launched
					it1.close();
					currentIterator = it2;
					return it2.getNext();
				}
				// if currentIterator was already it2, re-throw the exception
				throw new NoSuchElementException(e.getMessage());
			}

		}

		public void close() throws ModelAccessException {
			it1.close();
			it2.close();
		}

	}

	/**
	 * returns an iterator with removed duplicates from <code>it</code>
	 * 
	 * @param it
	 * @return
	 */
	public static ARTURIResourceIterator listDistinct(ARTURIResourceIterator it) {
		return createARTURIResourceIterator(new DistinctRDFIterator<ARTURIResource>(it));
	}

	/**
	 * returns an iterator with removed duplicates from <code>it</code>
	 * 
	 * @param it
	 * @return
	 */
	public static ARTResourceIterator listDistinct(ARTResourceIterator it) {
		return createARTResourceIterator(new DistinctRDFIterator<ARTResource>(it));
	}

	public static class DistinctRDFIterator<T> extends RDFIteratorImpl<T> {

		HashSet<T> explored;
		RDFIterator<T> resIt;
		T capsule = null;

		DistinctRDFIterator(RDFIterator<T> it) {
			this.resIt = it;
			explored = new HashSet<T>();
		}

		public void close() throws ModelAccessException {
			resIt.close();
		}

		public boolean streamOpen() throws ModelAccessException {
			T temp;
			while (resIt.streamOpen() == true && capsule == null) {
				temp = resIt.next();
				if (!explored.contains(temp)) {
					capsule = temp;
					explored.add(temp);
				}
			}
			if (capsule != null)
				return true;
			else
				return false;
		}

		public T getNext() throws ModelAccessException {
			T temp = capsule;
			capsule = null;
			return temp;
		}

	}

	/**
	 * This iterator class provides facilities for managing resources of type rdf:List It is already used in
	 * OWL and SKOS methods such as {@link OWLModelImpl#parseDataRange(ARTResource, ARTResource...)}
	 * 
	 * @author Armando Stellato
	 * 
	 */
	public static class RDFListIterator extends RDFIteratorImpl<ARTNode> implements ARTNodeIterator {

		ARTResource currentNode;
		OWLModel model;
		boolean inference;
		ARTResource[] graphs;

		public RDFListIterator(OWLModel model, ARTResource list, boolean inference, ARTResource... graphs)
				throws ModelAccessException {
			this.model = model;
			this.graphs = graphs;
			this.inference = inference;
			currentNode = list;
		}

		public void close() throws ModelAccessException {

		}

		public ARTNode getNext() throws ModelAccessException {
			if (!streamOpen())
				throw new ModelAccessException("cannot access next node of the list since there are no more!");

			// getting value of current node
			ARTNodeIterator it = model
					.listValuesOfSubjPredPair(currentNode, RDF.Res.FIRST, inference, graphs);
			ARTNode currentValue = it.getNext();
			it.close();

			// updating current node
			ARTResourceIterator resIt = model.listValuesOfSubjObjPropertyPair(currentNode, RDF.Res.REST,
					inference, graphs);
			currentNode = resIt.getNext();
			resIt.close();

			return currentValue;
		}

		public boolean streamOpen() throws ModelAccessException {
			if (currentNode.equals(RDF.Res.NIL))
				return false;
			return true;
		}

	}

	private static class ARTNodeIteratorImpl extends RDFIteratorImpl<ARTNode> implements ARTNodeIterator {

		RDFIterator<ARTNode> it;

		ARTNodeIteratorImpl(RDFIterator<ARTNode> it) {
			this.it = it;
		}

		public boolean streamOpen() throws ModelAccessException {
			return it.streamOpen();
		}

		public ARTNode getNext() throws ModelAccessException {
			return it.getNext();
		}

		public void close() throws ModelAccessException {
			it.close();
		}
	}

	private static class ARTBNodeIteratorImpl extends RDFIteratorImpl<ARTBNode> implements ARTBNodeIterator {

		RDFIterator<ARTBNode> it;

		ARTBNodeIteratorImpl(RDFIterator<ARTBNode> it) {
			this.it = it;
		}

		public boolean streamOpen() throws ModelAccessException {
			return it.streamOpen();
		}

		public ARTBNode getNext() throws ModelAccessException {
			return it.getNext();
		}

		public void close() throws ModelAccessException {
			it.close();
		}
	}

	private static class ARTResourceIteratorImpl extends RDFIteratorImpl<ARTResource> implements
			ARTResourceIterator {

		RDFIterator<ARTResource> it;

		ARTResourceIteratorImpl(RDFIterator<ARTResource> it) {
			this.it = it;
		}

		public boolean streamOpen() throws ModelAccessException {
			return it.streamOpen();
		}

		public ARTResource getNext() throws ModelAccessException {
			return it.getNext();
		}

		public void close() throws ModelAccessException {
			it.close();
		}
	}

	private static class ARTURIResourceIteratorWrappingStdIteratorImpl extends RDFIteratorImpl<ARTURIResource> implements
			ARTURIResourceIterator {

		Iterator<ARTURIResource> it;

		ARTURIResourceIteratorWrappingStdIteratorImpl(Iterator<ARTURIResource> it) {
			this.it = it;
		}

		public boolean streamOpen() throws ModelAccessException {
			return it.hasNext();
		}

		public ARTURIResource getNext() throws ModelAccessException {
			return it.next();
		}

		public void close() throws ModelAccessException {
			//it.close();
		}
	}

	private static class ARTURIResourceIteratorImpl extends RDFIteratorImpl<ARTURIResource> implements
			ARTURIResourceIterator {

		RDFIterator<ARTURIResource> it;

		ARTURIResourceIteratorImpl(RDFIterator<ARTURIResource> it) {
			this.it = it;
		}

		public boolean streamOpen() throws ModelAccessException {
			return it.streamOpen();
		}

		public ARTURIResource getNext() throws ModelAccessException {
			return it.getNext();
		}

		public void close() throws ModelAccessException {
			it.close();
		}
	}

	private static class ARTLiteralIteratorImpl extends RDFIteratorImpl<ARTLiteral> implements
			ARTLiteralIterator {

		RDFIterator<ARTLiteral> it;

		ARTLiteralIteratorImpl(RDFIterator<ARTLiteral> it) {
			this.it = it;
		}

		public boolean streamOpen() throws ModelAccessException {
			return it.streamOpen();
		}

		public ARTLiteral getNext() throws ModelAccessException {
			return it.getNext();
		}

		public void close() throws ModelAccessException {
			it.close();
		}
	}

	private static class ARTStatementIteratorImpl extends RDFIteratorImpl<ARTStatement> implements
			ARTStatementIterator {

		RDFIterator<ARTStatement> it;

		ARTStatementIteratorImpl(RDFIterator<ARTStatement> it) {
			this.it = it;
		}

		public boolean streamOpen() throws ModelAccessException {
			return it.streamOpen();
		}

		public ARTStatement getNext() throws ModelAccessException {
			return it.getNext();
		}

		public void close() throws ModelAccessException {
			it.close();
		}
	}

	private static class ARTStatementIteratorNormalIteratorImpl extends RDFIteratorImpl<ARTStatement>
			implements ARTStatementIterator {

		Iterator<ARTStatement> it;

		ARTStatementIteratorNormalIteratorImpl(Iterator<ARTStatement> it) {
			this.it = it;
		}

		public boolean streamOpen() throws ModelAccessException {
			return it.hasNext();
		}

		public ARTStatement getNext() throws ModelAccessException {
			return it.next();
		}

		public void close() throws ModelAccessException {
		}
	}

}
